Statement expressions

In RL, statements can be used as expressions, and expressions can be used as statements. Thus, a statement can evaluate into a single return value, and evaluating an expression can be command. This allows the use of statements as initialisers for variables, for example. To abort a statement expression and make it return a value as an expression, the = operator is used.

y ::= SWITCH(coin) {
    :heads:
        // All is well: y is not accessible within its own definition.
        = 2 * x + 3;
    :tails:
    {
        sqr ::= x * x;
        = -sqr*x + 5*sqr*sqr;
    }
(/ Implicitly generated, because a statement expression must either return or throw.
    DEFAULT:
        THROW; /)
};

With this, we can now mix functional and imperative programming freely, and make use of either technique whenever we deem the most appropriate. We can also do more complex calculations, such as using loops within expressions. Of course, in many cases, strictly sequential calculations or extracting functions is more readable and maintainable, but in some cases, the functional notation of computation is strictly superior. For example, translating the above code into strictly sequential code would require the creation of a new function or this mess:

y: DOUBLE; // y is uninitialised!
SWITCH(coin) {
:heads:
    // Danger: reading from y would be possible here!
    y := 2 * x + 3;
:tails:
{
    sqr ::= x * x;
    y := -sqr*x + 5*sqr*sqr;
}
}

This code requires that y be default-constructible, which for some types will lead to uninitialised storage, and for others, it will lead to a default value. Additionally, if the implicitly generated "throw" of the first code sample is not explicitly written in this approach, then the implicit default case of the switch will just leave y without an assigned value (or its default value, respectively). Thus, the first technique is actually easier to write secure code with, as it would just throw if the coin were defect, whereas the delayed initialisation of the second technique introduces intermediate states for variables, which can complicate the data types used. The second technique also introduces the possibility of reading a variable's intermediate value by accident.